#include <cstdint>
#include <iostream>
#include "log.hpp"
#include <stdio.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <signal.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/time.h>
#include <string.h>
#include <netdb.h>
#include <pthread.h>

const size_t PACKET_SEND_MAX_NUM =  0x40;
const size_t ADDR_SIZE = 0x80;
const size_t BUF_SIZE = 0x40;
const size_t RECV_SIZE = 0x200;

typedef struct ping_packet_status {

    struct timeval s_time;
    struct timeval e_time;
    int flag; 
    int seq; 

} ping_packet_status;

ping_packet_status ping_packet[PACKET_SEND_MAX_NUM];

int alive;
int sock;
int send_count;
int recv_count;
pid_t pid;
struct sockaddr_in dest;
struct timeval start_time;
struct timeval e_time;
struct timeval time_interval;

static void usage(std::string proc)
{
    std::cout << "\nUsage:" << proc << " ipAddress\n" << std::endl;
}

/*校验和算法*/
uint16_t checkSum(uint16_t* addr,int len)
{       int nleft=len;
        int sum = 0;
        auto w = addr;
        uint16_t answer=0;

        while(nleft > 1) {
            sum += *w++;
            nleft -= 2;
        }
        if(nleft == 1) {
            *(uint8_t*)(&answer) = *(uint8_t*)w;
            sum+=answer;
        }
        sum = (sum >> 0x10) + (sum & 0xffff);
        sum += (sum >> 0x10);
        answer = ~sum;
        return answer;
}

struct timeval calTime(struct timeval begin, struct timeval end)
{
    struct timeval ans;
    ans.tv_sec = end.tv_sec - begin.tv_sec;
    if((ans.tv_usec = end.tv_usec - begin.tv_usec) < 0) {
        --ans.tv_sec;
        ans.tv_usec += 1000000;
    }
    return ans;
}

void packIcmp(struct icmp* icmphdr, int seq, int length)
{
    icmphdr->icmp_type = ICMP_ECHO;
    icmphdr->icmp_code = 0;
    icmphdr->icmp_cksum = 0;
    icmphdr->icmp_seq = seq;
    icmphdr->icmp_id = pid & 0xffff;
    for(int i = 0; i < length; ++i) icmphdr->icmp_data[i] = i;
    icmphdr->icmp_cksum = checkSum((unsigned short*)icmphdr, length);
}

int unpackIcmp(char* buf, int len)
{
    int iphdr_len;
    struct timeval s_time, recv_time, offset_time;
    int rtt; 

    auto ip_hdr = (struct ip *)buf;
    iphdr_len = ip_hdr->ip_hl * 4;
    auto icmp = (struct icmp*)(buf+iphdr_len);
    len -= iphdr_len;
    if(len < 8) {
        logMessage(FATAL, "icmp packet len is less than 8!\n");
        return -1;
    }

    if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == (pid & 0xffff))) {
        if((icmp->icmp_seq < 0) || (icmp->icmp_seq > PACKET_SEND_MAX_NUM)) {
            logMessage(FATAL, "Invalid icmp_seq!\n");
            return -1;
        }

        ping_packet[icmp->icmp_seq].flag = 0;
        s_time = ping_packet[icmp->icmp_seq].s_time;
        gettimeofday(&recv_time, NULL);

        offset_time = calTime(s_time, recv_time);
        rtt = offset_time.tv_sec * 1000 + offset_time.tv_usec / 1000; 

        printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%d ms\n", len, inet_ntoa(ip_hdr->ip_src), icmp->icmp_seq, ip_hdr->ip_ttl, rtt);
    } else {
        logMessage(FATAL, "Invalid icmp packet!\n");
        return -1;
    }
    return 0;
}

void sendPing()
{
    char send_buf[BUF_SIZE];
    memset(send_buf, 0, sizeof(send_buf));
    gettimeofday(&start_time, nullptr);
    while(alive) {
        int size = 0;
        gettimeofday(&(ping_packet[send_count].s_time), nullptr);
        ping_packet[send_count].flag = 1; 

        packIcmp((struct icmp*)send_buf, send_count, sizeof send_buf); 
        size = sendto(sock, send_buf, sizeof send_buf, 0, (struct sockaddr*)&dest, sizeof dest);
        ++send_count; 
        if(size < 0) {
            logMessage(ERROR, "Fail to sendto! %d: %s\n", errno, strerror(errno));
            continue;
        }
        sleep(1);
    }
}

void recvPing()
{
    struct timeval tv;
    tv.tv_usec = 200; 
    tv.tv_sec = 0;
    fd_set read_fd;
    char recv_buf[RECV_SIZE];
    memset(recv_buf, 0 ,sizeof(recv_buf));
    while(alive) {
        int ret = 0;
        FD_ZERO(&read_fd);
        FD_SET(sock, &read_fd);
        ret = select(sock + 1, &read_fd, nullptr, nullptr, &tv);
        switch(ret) {
            case -1:
                fprintf(stderr,"fail to select!\n");
                break;
            case 0:
                break;
            default:
                {
                    int size = recv(sock, recv_buf, sizeof(recv_buf), 0);
                    if(size < 0) {
                        logMessage(ERROR, "Fail to recv! %d: %s\n", errno, strerror(errno));
                        continue;
                    }

                    ret = unpackIcmp(recv_buf, size);
                    if(ret == -1) continue;
                    ++recv_count; 
                }
                break;
        }
    }
}

void getTime(int signo)
{
    alive = 0;
    gettimeofday(&e_time, nullptr);
    time_interval = calTime(start_time, e_time);
}

void show()
{
    long time = time_interval.tv_sec * 1000 + time_interval.tv_usec / 1000;
    printf("%d packets transmitted, %d recieved, %d%c packet loss, time %ldms\n", send_count, recv_count, (send_count-recv_count)*100/send_count, '%', time);
}

